
#import <gamekit/gamekit.h>

@interface GKStage(private)
- _add:adds remove:removes with:theList; // helper for -doAddsAndRemoves
@end

@implementation GKStage

- init
{
	id ret = [super init];
	int i;
	for (i=0; i<=GK_ACTOR_TYPES; i++) {
		actors[i] = [[List alloc] init];
		addActors[i] = [[List alloc] init];
		removeActors[i] = [[List alloc] init];
	}
	collisionGroups = [[List alloc] init];
	addCollisionGroups = [[List alloc] init];
	removeCollisionGroups = [[List alloc] init];
	stageManager = nil;
	return ret;
}

- makeDirtPileForBuffer:(int)anInt
{	// you can use anInt to tweak each DirtPile differently to get better speed
	if (!anInt) return nil; // no DirtPile at level zero...
	return [[[DirtPile alloc] init] setAllDirty];
}

- setBufferType:(int)anInt to:aBuffer
{
	if ((anInt < 0) || (anInt > GK_BUFFER_TYPES)) return nil;
	drawingBuffers[anInt] = aBuffer;
	if (!anInt) return self;	// no dirtpile #0...
	if (!dirtPiles[anInt])
		dirtPiles[anInt] = [self makeDirtPileForBuffer:anInt];
	return self;
}

- dirtPileType:(int)anInt
{
	if ((anInt < 0) || (anInt > GK_BUFFER_TYPES)) return nil;
	return dirtPiles[anInt];
}

- bufferType:(int)anInt
{
	if ((anInt < 0) || (anInt > GK_BUFFER_TYPES)) return nil;
	return drawingBuffers[anInt];
}

- setStageManager:aManager { stageManager = aManager; return self; }
- stageManager { return stageManager; }

- actorListForType:(int)type
{
	return actors[type];
}

- addActor:anActor ofType:(int)type
{	// hold on to it; we can't add while we're in the middle of a frame
	// since it may affect the List object during a sweep of its contents
	if ((type >= 0) && (type <= GK_ACTOR_TYPES))
		[addActors[type] addObject:anActor];
	return self;
}

- removeActor:anActor ofType:(int)type
{	// hold on to it; we can't remove while we're in the middle of a frame
	// since it may affect the List object during a sweep of its contents
	if ((type >= 0) && (type <= GK_ACTOR_TYPES))
		[removeActors[type] addObject:anActor];
	return self;
}

- addCollisionGroup:aGroup
{
	[addCollisionGroups addObject:aGroup];
	return self;
}

- removeCollisionGroup:aGroup
{
	[removeCollisionGroups addObject:aGroup];
	return self;
}

- collisionGroups { return collisionGroups; }
- findCollisionGroupWithTag:(int)anInt;
{
	int i;
	for (i=0; i<[collisionGroups count]; i++) {
		id group = [collisionGroups objectAt:i];
		if ([group tag] == anInt) return group;
	}
	return nil;
}

- _add:adds remove:removes with:theList level:(int)level
{
	int j;
	for (j=0; j<[adds count]; j++) {
		id objectToAdd = [adds objectAt:j];
		if (level >= 0) {
			if ([objectToAdd respondsTo:@selector(setDrawingLevel:)])
					[objectToAdd setDrawingLevel:level];
			if ([objectToAdd respondsTo:@selector(setStage:)])
					[objectToAdd setStage:self];
		}
		[theList addObject:objectToAdd];
	}
	for (j=0; j<[removes count]; j++) {
		id anActor = [removes objectAt:j];
		[theList removeObject:anActor];
		[stageManager addDeadActor:anActor];
		// if not using a manager, free the object...
		if (!stageManager) [anActor free];
	}
	return self;
}

- doAddsAndRemoves
{	// _now_ it can take effect (actors at all levels and collision groups
	int i;
	for (i=0; i<=GK_ACTOR_TYPES; i++) {
		[self _add:addActors[i] remove:removeActors[i] with:actors[i] level:i];
	}
	[self _add:addCollisionGroups remove:removeCollisionGroups
			with:collisionGroups level:(-1)];
	return self;
}

- doOneFrame
{
	[self calculateMoves];
	[self calculateCollisions];
	if ([self doRender]) [self render];
	[self doAddsAndRemoves];
	return self;
}

- (BOOL)doRender { return YES; }

- calculateMoves
{
	int i;
	for (i=0; i<=GK_ACTOR_TYPES; i++) {
		[actors[i] makeObjectsPerform:@selector(move:) with:self];
	}
	return self;
}

- calculateCollisions
{
	[collisionGroups makeObjectsPerform:@selector(calculateCollisions:)
			with:self];
	return self;
}

- render
{
	register int i, j, k, t, lastLevel = 0; // lastLevel is the number of the
	// next lower buffer level.  If a buffer doesn't exists, this allows us
	// to skip it and use the next lowest (existing) buffer level.
	// The actors will only move if the GameView is in the normal state
	// and unpaused.  They can still advance the render state machine, though.
	BOOL shouldMove = ((![drawingBuffers[GK_SCREEN_BUFFER] isPaused]) &&
			([drawingBuffers[GK_SCREEN_BUFFER] realGameState] == NORMALSTATE));
	NXPoint offset;
	[drawingBuffers[GK_SCREEN_BUFFER] getOffset:&offset];
	// Loop through each buffer, allowing changes to propagate from
	// the lowest to the highest buffer
	for (i=1; i<GK_BUFFER_TYPES; i++) {
		// Make sure that this buffer level exists; if not, we skip to the
		// next level and push all actors in this level up to the next level.
		if (drawingBuffers[i]) {
			id thisDirtPile = nil;
			// find the DirtPile for the next highest level.  (If a level
			// doesn't have a buffer, we need to effectively skip it.)
			for (k=i+1; k<GK_BUFFER_TYPES; k++) { // if on last buffer,
				// (top level) loop is skipped, and so thisDirtPile = nil
				if (dirtPiles[k]) {
					thisDirtPile = dirtPiles[k];
					break;
			}	}
			[drawingBuffers[i] lockFocus];
			for (k=lastLevel+1; k<=i; k++) { // loop makes sure that any
				// actors on a level w/o a buffer still get drawn and erased,
				// but in the next higher buffer level.
				
				// erase everything on this level by marking it dirty in
				// the DirtPile for this level:  each actor sends it's
				// most recently drawn bounding box to the DirtPile.
				[actors[k] makeObjectsPerform:@selector(eraseInDirtPile:)
						with:dirtPiles[i]];
			}
			// Now, copy everything from the level below up to this level,
			// using the DirtPile for efficiency.  This makes all erasing
			// happen at once.
			[dirtPiles[i] sendDirtTo:thisDirtPile]; // next level needs to
			// know of all changes on lower levels.
			[dirtPiles[i] doRedraw:drawingBuffers[lastLevel]]; // go back
			// to the most recent buffer
			for (k=lastLevel+1; k<=i; k++) { // loop makes sure that any
				// actors that are in a level w/o a buffer still get drawn,
				// but in the next highest buffer level.
				
				// Next draw anything that gets drawn at this level (any actors
				// that are here.
				t = [actors[k] count]; // faster than a message call each
				// time thru the loop; requires that the list not change
				// during the render, however!!!
				for (j=0; j<t; j++) { // loop ourselves to be
					// sure objects are called from #0 to highest; allows user
					// to add actors in the order they are expected to be drawn
					// (or sort the actors' drawing order)
					[[actors[k] objectAt:j] renderAt:&offset
							move:shouldMove withDirtPile:thisDirtPile];
			}	}
			[drawingBuffers[i] unlockFocus];
			lastLevel = i;
	}	}
	return self;
}

@end
